package com.loser.utils;

import com.github.rholder.retry.Retryer;
import com.github.rholder.retry.RetryerBuilder;
import com.github.rholder.retry.StopStrategies;
import com.github.rholder.retry.WaitStrategies;
import com.loser.redis.core.RedisHandler;
import com.loser.utils.checker.fun.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

/**
 * 重试工具
 *
 * @author alan
 * @date 2022/5/23 19:26
 */
@Component
public class RetryUtil {

    private static final Logger log = LoggerFactory.getLogger(RetryUtil.class);

    /**
     * 重试调用方法-有返回值
     *
     * @param callable        业务方法
     * @param resultPredicate 重试条件
     * @param excRetry        异常是否重试
     * @param sleepTime       每次重试休眠时间
     * @param retryTime       重试次数
     * @param <T>             判断值类型
     * @return 返回值
     */
    public static <T> T retry(Callable<T> callable, Predicate<T> resultPredicate, boolean excRetry, long sleepTime, int retryTime) {

        RetryerBuilder<T> builder = RetryerBuilder.<T>newBuilder()
                // 匹配到重试条件
                .retryIfResult(resultPredicate::apply)
                // 每次循环休眠时间
                .withWaitStrategy(WaitStrategies.fixedWait(sleepTime, TimeUnit.MILLISECONDS))
                // 设置最大执行次数N次
                .withStopStrategy(StopStrategies.stopAfterAttempt(retryTime));
        if (excRetry) {
            builder.retryIfRuntimeException();
        }
        Retryer<T> retry = builder.build();
        try {
            return retry.call(callable);
        } catch (Exception e) {
            log.error("RetryUtil retry throw a e", e);
            throw new BusinessException("操作过快");
        }

    }

    /**
     * 通过抢锁重试-有返回值
     *
     * @param callable  业务方法
     * @param sleepTime 每次重试睡眠时间
     * @param retryTime 重试次数
     * @param lockKey   锁
     * @param lockTime  锁的时长
     * @param <T>       返回值
     * @return 返回执行业务方法后的结果
     */
    public static <T> T retryByLock(Callable<T> callable, String lockKey, int lockTime, long sleepTime, int retryTime) {

        Retryer<T> retry = RetryerBuilder.<T>newBuilder()
                // 异常重试
                .retryIfRuntimeException()
                // 每次循环休眠时间
                .withWaitStrategy(WaitStrategies.fixedWait(sleepTime, TimeUnit.MILLISECONDS))
                // 设置最大执行次数N次
                .withStopStrategy(StopStrategies.stopAfterAttempt(retryTime)).build();
        try {
            return retry.call(() -> lockAndCall(callable, lockKey, lockTime));
        } catch (Exception e) {
            log.error("RetryUtil retry throw a e", e);
            throw new BusinessException("操作过快");
        }

    }

    /**
     * 通过抢锁重试-无返回值
     *
     * @param runnable  业务方法
     * @param sleepTime 每次重试睡眠时间
     * @param retryTime 重试次数
     * @param lockKey   锁
     * @param lockTime  锁的时长
     */
    public static void retryByLock(Runnable runnable, String lockKey, int lockTime, long sleepTime, int retryTime) {

        Retryer<Boolean> retry = RetryerBuilder.<Boolean>newBuilder()
                // 异常重试
                .retryIfRuntimeException()
                // 每次循环休眠时间
                .withWaitStrategy(WaitStrategies.fixedWait(sleepTime, TimeUnit.MILLISECONDS))
                // 设置最大执行次数N次
                .withStopStrategy(StopStrategies.stopAfterAttempt(retryTime)).build();
        try {
            retry.call(() -> lockAndRun(runnable, lockKey, lockTime));
        } catch (Exception e) {
            log.error("RetryUtil retry throw a e", e);
            throw new BusinessException("操作过快");
        }

    }

    /**
     * 抢锁重试
     */
    private static <T> T lockAndCall(Callable<T> callable, String lockKey, int lockTime) throws Exception {

        boolean lock = false;
        RedisHandler redisHandler = ContextUtil.getBean(RedisHandler.class);
        try {
            lock = redisHandler.lock(lockKey, lockKey, lockTime);
            if (!lock) {
                throw new BusinessException("retryUtil not get a lock throw a e");
            }
            return callable.call();
        } finally {
            if (lock) {
                redisHandler.releaseLock(lockKey, lockKey);
            }
        }

    }


    /**
     * 抢锁重试
     */
    private static Boolean lockAndRun(Runnable runnable, String lockKey, int lockTime) throws Exception {

        boolean lock = false;
        RedisHandler redisHandler = ContextUtil.getBean(RedisHandler.class);
        try {
            lock = redisHandler.lock(lockKey, lockKey, lockTime);
            if (!lock) {
                throw new BusinessException("retryUtil not get a lock throw a e");
            }
            runnable.run();
        } finally {
            if (lock) {
                redisHandler.releaseLock(lockKey, lockKey);
            }
        }
        return Boolean.TRUE;

    }

}



